#include <omp.h>
#include <cstdio>
#include <ctime>
#include <cstring>
#include <string>
#include <vector>
#include <map>

#include "pfs_item.h"
#include "SimulatedAnnealing.h"
#include "ii_pfs.h"
#include "tabu_pfs.h"
#include "coefficients.h"
#include "blind_pfs.h"

using std::string;
using std::vector;

static char* output_filename;
static char* input_filename;
static int moe_ts = 0;
static int test_rand = 0;

PFS_Item::JMTable ReadFile(FILE* fptr);
void AdjustII(FILE* output_file, const PFS_Item&);
void ReanCommand(int argc, char** argv);

void Experiment(FILE* output_file, coefficients coefficients_A, vector<string> filenames);
void MoeTSParams(FILE* output_file, vector<string> filenames, double total_time);
void PrintSolution(FILE*,const vector<int>& solu);
void TestRandom(const PFS_Item pfs_item, FILE* output_file);

int main(int argc, char** argv)
{
	// using RandomGen in GetRandomSolution() is a performance bottleneck
	// so I decide to offer old-style random function
	srand(time(NULL));

	clock_t program_start = clock();
	const int kItemNum = 9;
	string inputFolder = "PFSP_benchmark_data_set\\";
	string inputFileNames[] = {	
		"tai20_5_1.txt", "tai20_10_1.txt", "tai20_20_1.txt",
		"tai50_5_1.txt", "tai50_10_1.txt", "tai50_20_1.txt",
		"tai100_5_1.txt", "tai100_10_1.txt", "tai100_20_1.txt"
	};

	// default paths
	output_filename = "result.csv";
	input_filename  = "feed.txt";

	// read path and other params from command line
	ReanCommand(argc, argv);

	FILE* output_file = fopen(output_filename, "w");
	if(!output_file ) {
		fprintf(stderr, "Can not open file \"%s\"\n", output_filename);
		abort();
	}

	coefficients coefficients_A(input_filename);

	vector<string> input_file_paths;
	for (auto filename : inputFileNames)
		input_file_paths.push_back(inputFolder + filename);

	if (moe_ts) {
		MoeTSParams(output_file, input_file_paths, moe_ts);
	}
	else {
		Experiment(output_file, coefficients_A, input_file_paths);
	}
	

	fclose(output_file);

	return EXIT_SUCCESS;
}

PFS_Item::JMTable ReadFile(FILE* fptr) {
	int machine_num, job_num;
	fscanf(fptr, "%d %d %*s", &job_num, &machine_num);

	PFS_Item::JMTable table(machine_num, std::vector<int>(job_num));
	for (auto& machine : table) {
		for (auto& job : machine) {
			fscanf(fptr, "%d", &job);
		}
	}

	return table;
}

void AdjustII(FILE* output_file, const PFS_Item& pfs_item) {		
		using std::max;
		using std::min;
		int test_times = 20;
		double time_limit = 2.0;
		fprintf(output_file, "Retry. Num,Best,Worst,AVG\n");
		int ner_nums[] = {1};
		int ner_num = 1;
		//for (int ner_num : ner_nums) {
		int retry_limits[] = {0,1,2,4,8,16,32};
		for (int retry_limit : retry_limits) {
			fprintf(output_file, "%d,", retry_limit);
			int score = 0;
			int best = INT_MAX;
			int worst = 0;
			for(int whichTest = 0 ; whichTest < test_times ; ++whichTest) {
				printf("Ner: %d  Testing #%d...\n", ner_num, whichTest);			

				II_PFS ii(pfs_item, time_limit , ner_num, retry_limit);
				int result = ii.Calculate().score;
				score += result;
				best = min(result, best);
				worst = max(result, worst);
			}
			fprintf(output_file, "%d,%d,%lf\n", best, worst, static_cast<double>(score)/test_times);
		}
	//	}
}


void ReanCommand(int argc, char** argv) {
	for (int i = 0 ; i < argc-1 ; ++i) {

		if (strcmp(argv[i], "-i") == 0)
			input_filename = argv[i+1];

		else if (strcmp(argv[i], "-o") == 0)
			output_filename = argv[i+1];

		else if(strcmp(argv[i], "-m") == 0)
			moe_ts = std::stoi(argv[i+1]) * 60;

		else if(strcmp(argv[i], "-m") == 0)
			test_rand = std::stoi(argv[i+1]) * 60;	

	}
}

void Experiment(FILE* output_file, coefficients coefficients_A, vector<string> filenames) {
	time_t now = time(0);
	
	fprintf(output_file , "%s" , coefficients_A.to_string().c_str());

	for (int whichItem = 0 ; whichItem < filenames.size() ; ++whichItem)
	{
		string filename = filenames[whichItem];
		FILE* fptr = fopen(filename.c_str(), "r");
		if(!fptr) {
			fprintf(stderr, "Can not open file \"%s\"\n", filename.c_str());
			abort();
		}

		PFS_Item pfs_item(ReadFile(fptr));
		fclose(fptr);
		
		printf("\nComputing %d(th) item...\n", whichItem);
		fprintf(output_file, "%s\n", filename.c_str());

		if(test_rand) {
			TestRandom(pfs_item, output_file);
		}
		
		if (coefficients_A.II) { fprintf(output_file, ",II_score,II_solution"); }
		if (coefficients_A.SA) { fprintf(output_file, ",SA_score,SA_solution"); }
		if (coefficients_A.TS) { fprintf(output_file, ",TS_score,TS_solution"); }
		fprintf(output_file, "\n");

		for(int whichTest = 0 ; whichTest < coefficients_A.test_times ; ++whichTest)
		{
			printf("Testing #%d...\n", whichTest);
			
			if(coefficients_A.II) {
				II_PFS ii(pfs_item,
									coefficients_A.time_limit,
									coefficients_A.II_neighbor_per_time,
									coefficients_A.II_retry_limit);
				auto ans = ii.Calculate();
				fprintf(output_file, ",%d,",ans.score);
				PrintSolution(output_file, ans.solution);
			}

			if(coefficients_A.SA) {
				SA_PFS sa(pfs_item,
									coefficients_A.time_limit,
									coefficients_A.SA_start_tempture,
									coefficients_A.SA_init_cool_rate,
									coefficients_A.SA_final_temperature,
									coefficients_A.SA_iterate_length);
				auto ans = sa.Calculate();
				fprintf(output_file, ",%d,",ans.score);
				PrintSolution(output_file, ans.solution);
			}
			
			if(coefficients_A.TS) {
				Tabu_PFS tabu(pfs_item,
											coefficients_A.time_limit,
											coefficients_A.TS_neighbor_per_time,
											coefficients_A.TS_retry_limit,
											coefficients_A.TS_tubu_list_len);
				auto ans = tabu.Calculate();
				fprintf(output_file, ",%d,",ans.score);
				PrintSolution(output_file, ans.solution);
			}
			fprintf(output_file, "\n");
		}

		fprintf(output_file, "\n\n\n");
		
	}

	time_t end_time = time(0);
	fprintf(output_file , "Experiment Start at,%s" , ctime(&now));
	fprintf(output_file , "Experiment End at,%s" , ctime(&end_time));
}

void MoeTSParams(FILE* output_file, vector<string> filenames, double total_time) {
	auto filename = filenames[7];
	FILE* fptr = fopen(filename.c_str(), "r");
	if(!fptr) {
		fprintf(stderr, "Can not open file \"%s\"\n", filename.c_str());
		abort();
	}
	PFS_Item pfs_item(ReadFile(fptr));
	fclose(fptr);

	int rand_max = 1000;
	RandomGen<int> rand_num(0, rand_max);


	const double time_limit = 0.5;
	const int    tests_per_time = 4;
	const size_t num_params = 3;
	const int init_params[] = {6,12,40};
	int mutation_modulus[] = {1,2,8};
	vector<int> params(num_params), new_params;
	for (size_t i = 0 ; i < num_params ; ++i)
		params[i] = init_params[i];
		
	vector<Tabu_PFS> Tabus;
	Tabus.push_back(Tabu_PFS(pfs_item, time_limit, params[0], params[1], params[2]));

	int old_score = INT_MAX;
	int test_limit = static_cast<int>(total_time / (time_limit * tests_per_time));	
	fprintf(output_file, "Round,P0,P1,P3,Score\n");
	for (int i = 0 ; i < test_limit ; ++i) {
		if (i % 20 == 0) {
			fprintf(output_file, "%d,%d,%d,%d,%d,\n", i, params[0], params[1], params[2], old_score);
			printf("%d,%d,%d,%d,%d,\n", i, params[0], params[1], params[2], old_score);
		}

		new_params = params;

		for(size_t j = 0 ; j < new_params.size() ; ++j) {
			new_params[j] += mutation_modulus[j] * (rand_num() - (rand_max/2)) * 3.8 / rand_max;
			if(new_params[j] < 0) { new_params[j] = 0; }
		}
		
		int new_score = 0;
		for (int j = 0 ; j < tests_per_time ; ++j) {
			Tabu_PFS tabu(pfs_item, time_limit, params[0], params[1], params[2]);
			new_score += tabu.Calculate().score;
		}

		if (new_score < old_score) {
			params = new_params;
			old_score = new_score;
		}

	}

	fprintf(output_file, "total_time,%lf\n", total_time);
	fprintf(output_file, "time_limit,%lf\n", time_limit);
	fprintf(output_file, "tests_per_time,%d\n", tests_per_time);

}

void PrintSolution(FILE* fptr, const vector<int>& solu) {
	for (int e : solu)
		fprintf(fptr,"%d ", e);
}


void TestRandom(const PFS_Item pfs_item, FILE* output_file) {
	double time_limit = 1.0;

	fprintf(output_file, "Time spend(second),Best Score\n");

	int best_score = INT_MAX;
	for (int i = 0 ; i < test_rand ; ++i) {
		Blind_PFS rand_pfs(pfs_item, time_limit);
		int score = rand_pfs.Calculate().score;

		if (score < best_score)
			best_score = score;

		if(i % 10 == 0){
			fprintf(output_file, "%d,%d\n", i,best_score);
			fprintf(stdout, "%d,%d\n", i,best_score);
		}
	}

	

}